﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Microsoft.Research.Kinect.Nui;
using Coding4Fun.Kinect.Wpf;
using Blake.NUI.WPF.Utility;
using Kinductor.Gestures;
using Kinductor.Midi;
using System.Windows.Threading;

using Kinductor.ViewModels;
using System.Windows.Media.Animation;

namespace Kinductor.Views
{
    /// <summary>
    /// Interaction logic for Main.xaml
    /// </summary>
    public partial class Main : UserControl
    {
        #region Fields

        Brush defaultFill = new SolidColorBrush(Colors.Green);
        Brush activeFill = new SolidColorBrush(Colors.Blue);

        BeatPatternRecognizer beatPattern;
        GazeDetector gazeDetection;

        Dictionary<JointID, Ellipse> ellipses = new Dictionary<JointID, Ellipse>();
        Rectangle wand = new Rectangle();
        String[] sections = { "Percussion", "Strings1", "Horns", "Woodwinds", "Strings2" };
        bool wandCreated = false;
        DispatcherTimer volumeHideSlider;
        MidiController midi;

        #endregion

        public Main()
        {
            InitializeComponent();

            defaultFill.Freeze();
            activeFill.Freeze();
        }

        public void Init()
        {
            Sections sectionsEx;

            KinductorController.LoadRuntimeParams();

            KinductorController.nui.DepthFrameReady += new EventHandler<ImageFrameReadyEventArgs>(nui_DepthFrameReady);


            KinductorController.OpenDepthStream(2);

            beatPattern = new BeatPatternRecognizer(KinductorController.nui);
            beatPattern.BeatPatternUpdated += new EventHandler(beatPattern_BeatPatternUpdated);
            gazeDetection = new GazeDetector(KinductorController.nui, beatPattern);
            gazeDetection.GazeDetectorUpdated += new EventHandler(gazeDetection_GazeDetectorUpdated);

            volumeSlider.Hide(false);

            //string filename = @"c:\temp\midi\1812ovrt.mid";
            string filename = @"C:\temp\midi\clip.mid";
            midi = new MidiController();
            midi.FileLoaded += new EventHandler(midi_FileLoaded);
            midi.PlayingStatusChanged += new EventHandler(midi_PlayingStatusChanged);
            midi.PlayingCompleted += new EventHandler(midi_PlayingCompleted);
            midi.ChannelNoteOn += new EventHandler<ChannelNoteEventArgs>(midi_ChannelNoteOn);
            midi.LoadProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(midi_LoadProgressChanged);
            midi.LoadFile(filename);

            midi.PositionChanged += new EventHandler(midi_PositionChanged);

            channelSections.Add(0, imgPercussion);
            channelSections.Add(1, imgWoodwinds);
            channelSections.Add(2, imgWoodwinds);
            channelSections.Add(3, imgHorns);
            channelSections.Add(4, imgHorns);
            channelSections.Add(5, imgStrings1);
            channelSections.Add(6, imgStrings2);
            channelSections.Add(7, imgStrings1);
            channelSections.Add(8, imgStrings2);
            channelSections.Add(9, imgPercussion);

            for (int i = 0; i <= 9; i++)
            {
                midi.channelVolumeOverride[i] = 127;
            }


            pulseTimer = new DispatcherTimer();
            pulseTimer.Tick += new EventHandler(pulseTimer_Tick);
            pulseTimer.Interval = TimeSpan.FromSeconds(0.05);
            pulseTimer.Start();

            volumeHideSlider = new DispatcherTimer();
            volumeHideSlider.Tick += new EventHandler(volumeHideSlider_Tick);
            volumeHideSlider.Interval = TimeSpan.FromSeconds(4);

        }

        public void TogglePlay()
        {
            if (!midi.IsFileLoaded)
                return;
            if (midi.IsPlaying)
                midi.Stop();
            else
                midi.Play();
        }

        void volumeHideSlider_Tick(object sender, EventArgs e)
        {
            volumeSlider.Hide();
            volumeHideSlider.Stop();
        }

        void pulseTimer_Tick(object sender, EventArgs e)
        {
            foreach (var kvp in channelSections)
            {
                if (kvp.Value.Opacity > 0)
                    kvp.Value.Opacity -= 0.05;

            }
        }

        Dictionary<int, Image> channelSections = new Dictionary<int, Image>();
        DispatcherTimer pulseTimer;
        void midi_ChannelNoteOn(object sender, ChannelNoteEventArgs e)
        {
            if (!channelSections.ContainsKey(e.ChannelId))
            {
                return;
            }
            var image = channelSections[e.ChannelId];
            image.Opacity = Math.Min(image.Opacity + 1.0, 1);
            //Dispatcher.BeginInvoke((Action)delegate
            //{
            //    image.Opacity = 0.5;
            //});
            //imgHorns.Opacity = 1;
            //PulseImage(1, 0.4, image);

        }

        void midi_PositionChanged(object sender, EventArgs e)
        {
            sheetMusic.Position = midi.Position;
        }

        void midi_LoadProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
        {
            //txtMidiStatus.Text = "Loading " + e.ProgressPercentage.ToString() + "%";
        }

        void midi_PlayingCompleted(object sender, EventArgs e)
        {
            midi.Play();
        }

        void midi_PlayingStatusChanged(object sender, EventArgs e)
        {
        }

        void midi_FileLoaded(object sender, EventArgs e)
        {
            //midi.Play();
            //txtMidiStatus.Text = "Loaded";
            PulseImage(1, .5, pulse);
        }

        void gazeDetection_GazeDetectorUpdated(object sender, EventArgs e)
        {
            //right now using .6 to represent approximate width of podium
            int directionX = getDirectionX(gazeDetection.GazeX, 3, 0.6);
            int directionY = getDirectionY(gazeDetection.GazeY, 2, -.1);
            double directionZ = gazeDetection.GazeZ;
            //sectionsEx = getOrchestraSection(directionX, directionY);

            //txtGaze.Text = "X: " + gazeDetection.GazeX.ToString("F1") +" b: " + directionX + " Y: " + gazeDetection.GazeY.ToString("F1") + " b: " + directionY;
            return;
            switch (getOrchestraSection(directionX, directionY, directionZ))
            {
                case Sections.HORNS:

                    for (int i = 0; i <= 9; i++)
                    {
                        midi.channelVolumeOverride[i] = 40;
                    }
                    midi.channelVolumeOverride[3] = 127;
                    midi.channelVolumeOverride[4] = 127;
                    channelSections[3].Opacity = 10;
                    channelSections[4].Opacity = 10;
                    break;
                case Sections.PERCUSSION:
                    break;
                case Sections.STRINGS1:

                    for (int i = 0; i <= 9; i++)
                    {
                        midi.channelVolumeOverride[i] = 40;
                    }
                    midi.channelVolumeOverride[5] = 127;
                    midi.channelVolumeOverride[7] = 127;
                    channelSections[5].Opacity = 10;
                    channelSections[7].Opacity = 10;
                    break;
                case Sections.STRINGS2:
                    for (int i = 0; i <= 9; i++)
                    {
                        midi.channelVolumeOverride[i] = 40;
                    }
                    midi.channelVolumeOverride[6] = 127;
                    midi.channelVolumeOverride[8] = 127;
                    channelSections[6].Opacity = 10;
                    channelSections[8].Opacity = 10;
                    break;
                case Sections.WOODWINDS:
                    break;
                case Sections.ALL:
                default:

                    for (int i = 0; i <= 9; i++)
                    {
                        midi.channelVolumeOverride[i] = midi.MasterVolumeOverride;
                    }
                    break;
            }

            //OrchestraPit.Source = bmp;
        }

        enum Sections
        {
            PERCUSSION = 1,
            STRINGS1 = 2,
            HORNS = 3,
            WOODWINDS = 4,
            STRINGS2 = 5,
            ALL = 6
        }

        Sections getOrchestraSection(int directionX, int directionY, double directionZ)
        {
            if (directionZ <= 1 && directionX == 2)
                return Sections.HORNS;
            else if (directionX == 1)
                return Sections.PERCUSSION;
            else if (directionX == 3)
                return Sections.STRINGS2;
            /*else if (directionX == 1 && directionY == 0)
                return Sections.STRINGS1;
            else if (directionX == 2 && directionY == 1)
                return Sections.HORNS;
            else if (directionX == 2 && directionY == 0)
                return Sections.WOODWINDS;
            else if (directionX == 3)
                return Sections.STRINGS2;
            */
            else
                return Sections.ALL;
        }

        //int getOrchestraSection(int directionX, int directionY)
        //{
        //    if (directionX == 1 && directionY == 1)
        //        return 1;
        //    else if ((directionX == 1 && directionY == 0) || directionX == 3)
        //        return 2;
        //    else if (directionX == 2 && directionY == 1)
        //        return 3;
        //    else if (directionX == 2 && directionY == 0)
        //        return 4;
        //    else
        //        return 0;
        //}

        int getDirectionY(double y, int buckets, double cutOffY)
        {
            //don't use rangeY right now
            if (y <= cutOffY)
                return 0;
            else
                return 1;
        }

        int getDirectionX(double x, int buckets, double rangeX)
        {
            //pick out of n buckets
            //range is centered in middle if width of screen

            int whichBucket;

            //hardcoded right now
            double leftMostX = rangeX / 2 * -1;
            double stepSize = rangeX / buckets;

            //if out of bounds, return closest bucket
            for (whichBucket = 1; whichBucket <= buckets; whichBucket++)
            {
                if (x <= (leftMostX + stepSize * whichBucket))
                {
                    return whichBucket;
                }
            }

            //if here, in last bucket
            return whichBucket - 1;
        }

        void beatPattern_BeatPatternUpdated(object sender, EventArgs e)
        {
            if (midi.IsPlaying)
            {
                midi.OverrideVolume = true;
                if (beatPattern.IsBeatingActive)
                {
                    midi.Tempo = beatPattern.BeatsPerMinute;
                }

                if (beatPattern.IsVolumeDeltaActive)
                {
                    midi.MasterVolumeOverride = MathUtility.MapValue(beatPattern.VolumeDelta, 0, 1, 0, 127);
                    midi.OverrideVolume = true;
                    volumeSlider.SetVolume(beatPattern.VolumeDelta);
                }

                if (!beatPattern.IsConducting)
                {
                    midi.Tempo = -1;
                    midi.OverrideVolume = false;
                }
            }

            if (beatPattern.IsVolumeDeltaActive)
            {
                volumeSlider.Show(false);
                volumeHideSlider.Stop();
                volumeHideSlider.Start();
            }

            //UpdateTextBlocks();

            var skeleton = beatPattern.Skeleton;
            foreach (var joint in skeleton.Joints.OfType<Joint>())
            {

                Ellipse ellipse = null;
                if (!ellipses.TryGetValue(joint.ID, out ellipse))
                {
                    ellipse = new Ellipse();
                    ellipse.Fill = defaultFill;
                    ellipse.Width = 10;
                    ellipse.Height = 10;
                    ellipses.Add(joint.ID, ellipse);
                    canvas.Children.Add(ellipse);
                    ellipse.Visibility = System.Windows.Visibility.Collapsed;
                }

                SetEllipsePosition(ellipse, joint);
            }

            UpdateHandPulses();
        }

        private void UpdateTextBlocks()
        {
            //txtBPM.Text = beatPattern.BeatsPerMinute.ToString("F1");
            //txtConfidence.Text = beatPattern.BeatConfidence.ToString("F1");
            //txtStyle.Text = beatPattern.Style.ToString();
            //txtVolume.Text = midi.MasterVolumeOverride.ToString("F1");
            //txtIsConducting.Text = beatPattern.IsConducting.ToString();
            //txtRightPhase.Text = beatPattern.rightHandStatus.Phase.ToString();
            //txtLeftPhase.Text = beatPattern.leftHandStatus.Phase.ToString();

            //txtHandAccel.Text = String.Concat(beatPattern.rightHandStatus.LastWristPosition.X.ToString("F3"), ", ", beatPattern.rightHandStatus.LastWristPosition.Y.ToString("F3"));
            //txtWristAccel.Text = String.Concat(beatPattern.rightHandStatus.LastWristPosition.X.ToString("F3"), ", ", beatPattern.rightHandStatus.LastWristPosition.Y.ToString("F3"));
            //txtShoulderAccel.Text = String.Concat(beatPattern.rightHandStatus.LastShoulderPosition.X.ToString("F3"), ", ", beatPattern.rightHandStatus.LastShoulderPosition.Y.ToString("F3"));
            ////txtDelta.Text = beatPattern.rightHandStatus.Volume.ToString("F3");
            //txtIctus.Text = String.Concat(beatPattern.rightHandStatus.LastIctusPosition.X.ToString("F3"), ", ", beatPattern.rightHandStatus.LastIctusPosition.Y.ToString("F3"));
            //txtIctusDelta.Text = String.Concat(beatPattern.rightHandStatus.IctusDelta.Milliseconds.ToString("F3"));
            //txtHandVelocity.Text = String.Concat(beatPattern.rightHandStatus.LastWristVelocity.X.ToString("F3"), ", ", beatPattern.rightHandStatus.LastWristVelocity.Y.ToString("F3"));
        }

        BackEase ease = new BackEase() { EasingMode = EasingMode.EaseIn };

        private void UpdateHandPulses()
        {
            if (ellipses.ContainsKey(JointID.HandRight))
            {
                var hand = ellipses[JointID.HandRight];
                if (beatPattern.rightHandStatus.IsPulsing)
                {
                    hand.Fill = activeFill;
                    hand.Width = 40;
                    hand.Height = 40;

                    if (beatPattern.IsBeatingActive)
                    {
                        var secPerBeat = 0.5 * 60.0 / beatPattern.BeatsPerMinute;
                        PulseImage(secPerBeat, 0.3, pulse);
                    }
                }
                else
                {
                    hand.Fill = defaultFill;
                    hand.Width = 10;
                    hand.Height = 10;
                }
            }

            if (ellipses.ContainsKey(JointID.HandLeft))
            {
                var hand = ellipses[JointID.HandLeft];
                if (beatPattern.leftHandStatus.IsPulsing)
                {
                    hand.Fill = activeFill;
                    hand.Width = 40;
                    hand.Height = 40;
                }
                else
                {
                    hand.Fill = defaultFill;
                    hand.Width = 10;
                    hand.Height = 10;
                }
            }
        }

        private void PulseImage(double len, double startOpacity, FrameworkElement element)
        {
            AnimateUtility.StopAnimation(element, FrameworkElement.OpacityProperty);
            pulse.Opacity = startOpacity;
            AnimateUtility.AnimateElementDouble(element, FrameworkElement.OpacityProperty, 0, 0, len, ease);
        }

        private void SetEllipsePosition(FrameworkElement ellipse, Joint joint)
        {
            float x;
            float y;
            KinductorController.SetSkeletonDepth(joint, out x, out y);
            Canvas.SetLeft(ellipse, x * 320 - ellipse.Width / 2);
            Canvas.SetTop(ellipse, y * 240 - ellipse.Height / 2);

        }

        void nui_DepthFrameReady(object sender, ImageFrameReadyEventArgs e)
        {
            //Use Coding4Fun extension method on ImageFrame class for Depth
            //imageDepth.Source = e.ImageFrame.ToBitmapSource();

            if (beatPattern.Skeleton == null)
            {
                return;
            }
            byte[] ColoredBytes = GenerateColoredBytes(e.ImageFrame);

            //create an image based on returned colors

            PlanarImage image = e.ImageFrame.Image;
            imageDepth.Source = BitmapSource.Create(image.Width, image.Height, 96, 96, PixelFormats.Bgra32, null,
                ColoredBytes, image.Width * PixelFormats.Bgra32.BitsPerPixel / 8);

        }

        private byte[] GenerateColoredBytes(ImageFrame imageFrame)
        {

            int height = imageFrame.Image.Height;
            int width = imageFrame.Image.Width;

            //Depth data for each pixel
            Byte[] depthData = imageFrame.Image.Bits;


            //colorFrame contains color information for all pixels in image
            //Height x Width x 4 (Red, Green, Blue, empty byte)
            Byte[] colorFrame = new byte[imageFrame.Image.Height * imageFrame.Image.Width * 4];

            //Bgr32  - Blue, Green, Red, empty byte
            //Bgra32 - Blue, Green, Red, transparency 
            //You must set transparency for Bgra as .NET defaults a byte to 0 = fully transparent

            //hardcoded locations to Blue, Green, Red (BGR) index positions       
            const int BlueIndex = 0;
            const int GreenIndex = 1;
            const int RedIndex = 2;
            const int AlphaIndex = 3;

            var depthIndex = 0;
            for (var y = 0; y < height; y++)
            {

                var heightOffset = y * width;

                for (var x = 0; x < width; x++)
                {
                    var index = ((x) + heightOffset) * 4;
                    var distance = GetDistanceWithPlayerIndex(depthData[depthIndex], depthData[depthIndex + 1]);

                    //Color a player
                    var pindex = GetPlayerIndex(depthData[depthIndex]);
                    if (pindex == beatPattern.PlayerIndex)
                    {
                        colorFrame[index + BlueIndex] = 0;
                        colorFrame[index + GreenIndex] = 0;
                        colorFrame[index + RedIndex] = 0;

                        if (y > height - 80)
                        {
                            colorFrame[index + AlphaIndex] = (byte)MathUtility.MapValue(y, height - 80, height, 255, 0);
                        }
                        else
                        {
                            colorFrame[index + AlphaIndex] = 255;
                        }
                    }
                    else
                    {
                        colorFrame[index + AlphaIndex] = 0;
                    }

                    //jump two bytes at a time
                    depthIndex += 2;
                }
            }

            return colorFrame;
        }

        private int GetDistanceWithPlayerIndex(byte firstFrame, byte secondFrame)
        {
            //offset by 3 in first byte to get value after player index 
            int distance = (int)(firstFrame >> 3 | secondFrame << 5);
            return distance;
        }

        private double GetDistance(double x, double y, double xx, double yy)
        {
            return Math.Sqrt((x - xx) * (x - xx) + (y - yy) * (y - yy));
        }

        private static int GetPlayerIndex(byte firstFrame)
        {
            //returns 0 = no player, 1 = 1st player, 2 = 2nd player...
            //bitwise & on firstFrame
            return (int)firstFrame & 7;
        }

        public void Unload()
        {
            KinductorController.UnloadKinect();
            midi.Dispose();
        }

    }
}
